package mmc.server.shell;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;

import mmc.Settings;
import mmc.server.shell.ops.ClassMemberReference;
import mmc.server.shell.ops.EngineSetting;
import mmc.server.shell.ops.MMCClass;
import mmc.server.shell.ops.Operation;
import mmc.server.shell.ops.PriorityType;
import mmc.server.shell.ops.Variable;
import mmc.server.shell.ops.baseops.AddAssignment;
import mmc.server.shell.ops.baseops.Addition;
import mmc.server.shell.ops.baseops.Assignment;
import mmc.server.shell.ops.baseops.Division;
import mmc.server.shell.ops.baseops.Equality;
import mmc.server.shell.ops.baseops.GreaterThan;
import mmc.server.shell.ops.baseops.LowerThan;
import mmc.server.shell.ops.baseops.Modulo;
import mmc.server.shell.ops.baseops.Multiplication;
import mmc.server.shell.ops.baseops.Not;
import mmc.server.shell.ops.baseops.PostDecrement;
import mmc.server.shell.ops.baseops.PostIncrement;
import mmc.server.shell.ops.baseops.Pow;
import mmc.server.shell.ops.baseops.PreDecrement;
import mmc.server.shell.ops.baseops.PreIncrement;
import mmc.server.shell.ops.baseops.SubAssignment;
import mmc.server.shell.ops.baseops.Subtraction;
import mmc.server.shell.ops.constant.BracketLayer;
import mmc.server.shell.ops.constant.StringValue;
import mmc.server.shell.ops.constant.Value;
import mmc.server.shell.ops.function.AVG;
import mmc.server.shell.ops.function.Assert;
import mmc.server.shell.ops.function.AssertEquals;
import mmc.server.shell.ops.function.Dir;
import mmc.server.shell.ops.function.For;
import mmc.server.shell.ops.function.Help;
import mmc.server.shell.ops.function.New;
import mmc.server.shell.ops.function.Printf;
import mmc.server.shell.ops.function.Set;
import mmc.server.shell.ops.function.Sum;
import mmc.server.shell.ops.utility.Debug;
import mmc.server.shell.ops.utility.EngineConstant;
import mmc.server.shell.ops.utility.FlagCellsDirty;
import mmc.server.shell.ops.utility.GetBlock;
import mmc.server.shell.ops.utility.PlayerLink;
import mmc.server.shell.ops.utility.SetBlock;
import mmc.server.shell.ops.utility.Spawn;
import mmc.server.shell.ops.utility.Status;
import mmc.server.shell.ops.utility.Who;

public class Shell {

	public static Map<String,Variable> environment = java.util.Collections.synchronizedMap(new TreeMap<String,Variable>());
	
    static public Vector<Class<? extends Operation>> operations;
    
    //TRACE-LEVEL higher -> more debug output
    public static int OP_PARSE_TRACE = 0;
    public static int OP_PARSE_TRACE_INDENT=0;
    public static int OP_LOAD_TRACE = 0;
    
    static {
        try {
            reloadOperations();
        } catch (Throwable t) {
            t.printStackTrace();
            throw new ExceptionInInitializerError(t);
        }
    }
    
    public static String prompt = "-> ";
    public static PrintWriter out;
    private static long calcStartTime;
    
    public static void checkTime(){
    	if(System.nanoTime() > calcStartTime + Settings.SHELL_MAX_CALC_TIME)
    		throw new RuntimeException("calculation exceeded allowed time!");
    }
    
    public static String interpret(String text) throws ShellException {
    	if(text.startsWith("#"))
    		return "";
    	
    	calcStartTime = System.nanoTime();
    	
    	CharArrayWriter data = new CharArrayWriter();
    	out = new PrintWriter(data);
    	
    	Operation tree = getOperationTreeFor(text);
    	if(tree.hasOutput()){
	    	if(tree.hasDoubleOutput()){
	    		double ret = tree.execute();
		    	out.println(ret);
	    	}else{
	    		Object o = tree.executeComplex();
	    		out.println(tree.getOutputType().getSimpleName()+": "+o);
	    	}
    	}else{
    		tree.executeComplex();
    	}
    	
    	out.flush();
    	out = null;
    	
    	return data.toString();
    }

    public static Operation getOperationTreeFor(String text) throws ShellException {
        try {
            for (Class<? extends Operation> op : operations) {
                try {
                    return op.getConstructor(String.class).newInstance(text);
                } catch (InvocationTargetException e) {
                    if (OP_PARSE_TRACE > 1) {
                        System.out.println("   ==============DEBUG****  matching " + op.toString() + " to " + text);
                        if (e.getCause() != null && e.getCause() instanceof mmc.server.shell.OperationNotAcceptedException) {
                            System.out.println("     Operation Not Accepted: " + e.getCause().getMessage());
                        } else {
                            e.getCause().printStackTrace(System.out);
                        }
                    }
                    if (e.getCause() != null && e.getCause() instanceof OperationNotAcceptedException && ((OperationNotAcceptedException) e.getCause()).critical) {
                        throw new OperationNotAcceptedException(false, op.getSimpleName() + ": " + e.getCause().getMessage());
                    }
                }
            }
        } catch (ShellException e) {
            throw e;
        } catch (Exception e) {
            throw new ShellException(e);
        }

        throw new OperationNotAcceptedException(false, "No Operation found for input: " + text);
    }

    public static void reloadOperations() throws ClassNotFoundException {
        Vector<Class<? extends Operation>> loperations = new Vector<Class<? extends Operation>>();

        //load
        loperations.add(Addition.class);
        loperations.add(Division.class);
        loperations.add(Modulo.class);
        loperations.add(Not.class);
        loperations.add(Multiplication.class);
        loperations.add(Pow.class);
        loperations.add(Subtraction.class);
        loperations.add(Assignment.class);
        loperations.add(AddAssignment.class);
        loperations.add(SubAssignment.class);
        loperations.add(PostIncrement.class);
        loperations.add(PreIncrement.class);
        loperations.add(PostDecrement.class);
        loperations.add(PreDecrement.class);
        loperations.add(GreaterThan.class);
        loperations.add(LowerThan.class);
        loperations.add(Equality.class);
        loperations.add(ClassMemberReference.class);
        
        loperations.add(BracketLayer.class);
        loperations.add(StringValue.class);
        loperations.add(Value.class);
        loperations.add(Variable.class);
        loperations.add(MMCClass.class);
        
        loperations.add(AVG.class);
        loperations.add(Help.class);
        loperations.add(Sum.class);
        loperations.add(For.class);
        loperations.add(Printf.class);
        loperations.add(New.class);
        loperations.add(Dir.class);
        loperations.add(Set.class);
        loperations.add(Assert.class);
        loperations.add(AssertEquals.class);
        
        loperations.add(EngineSetting.class);
        loperations.add(EngineConstant.class);
        loperations.add(Spawn.class);
        loperations.add(Who.class);
        loperations.add(SetBlock.class);
        loperations.add(GetBlock.class);
        loperations.add(Status.class);
        loperations.add(FlagCellsDirty.class);
        loperations.add(Debug.class);
        loperations.add(PlayerLink.class);

        //sort by PriorityType
        for (int i = 0; i < loperations.size()-1; i++) {
            for (int j = i+1; j < loperations.size(); j++) {
                PriorityType p1 = getPriorityType(loperations.get(i));
                PriorityType p2 = getPriorityType(loperations.get(j));
                
                //erstes Kriterium:  priorität
                //zweites Kriterium: funktionslänge (vergleich name (z.b. atan2 vor atan))
                
                if (p1.isPriorityLower(p2) ||
                        (p1.equals(p2) && loperations.get(i).getName().length() < loperations.get(j).getName().length())) {
                    Class<? extends Operation> tmp = loperations.get(i);
                    loperations.set(i, loperations.get(j));
                    loperations.set(j, tmp);
                }
            }
        }

        //apply
        operations = loperations;
        
        if(OP_LOAD_TRACE>0) {
            Shell.printOperatorPriorityList(System.out);
        }
    }

    public static void executeScriptFile(File f) throws IOException, ShellException{
    	try{
    		out = new PrintWriter(new OutputStreamWriter(System.out));
    		BufferedReader br = new BufferedReader(new FileReader(f));
	    	
	    	String line;
	    	while((line = br.readLine()) != null){
	    		System.out.println(/*Shell.prompt+*/line);
	    		
	    		if(line.length()!=0&&!line.startsWith("#")){
	    			calcStartTime = System.nanoTime();
	    			getOperationTreeFor(line).executeDefault();
	    		}
	    	}
	    	
	    	br.close();
	    	
    	}finally{
    		out = null;
    	}
    }
    
    private static PriorityType getPriorityType(Class<? extends Operation> c) {
        try {
            Method m = c.getMethod("getPriorityType");

            if (!Modifier.isStatic(m.getModifiers())) {
                throw new RuntimeException("method getPriorityType is not static!");
            }

            if (!m.getReturnType().isAssignableFrom(PriorityType.class)) {
                throw new RuntimeException("method getPriorityType has an invalid return type");
            }

            PriorityType p = (PriorityType)m.invoke(null);
            
            return p;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public static void printOperatorPriorityList(PrintStream out){
        out.println("List of Operators Sorted by Priority:");
        
        for(Class<? extends Operation> o : operations) {
            out.println("   "+getPriorityType(o) +": "+ o.getName());
        }
    }
}
